#include "TimeWheel.h"

/**
 * @brief Create a time wheel unit
 * @param size the size of list (2^size - 1)
 * @return twu* | NULL
 */
twu_t* tw_create_twu(uint32_t size, uint32_t offset)
{
    int i;
    twu_t *ptwu = NULL;
    size = (1<<size);
    ptwu = twMalloc(sizeof(twu_t) + sizeof(list_t*) * size);
    if(ptwu == NULL) return NULL;
    for (i = 0; i < size; ++i)
    {
        ptwu->list[i] = listCreate();
        if (ptwu->list[i] == NULL)
        {
            while (i--)
            {
                listDestroy(&ptwu->list[i]);
            }
            twFree(ptwu);
            return NULL;
        }
    }
    ptwu->size = size;
    ptwu->tick = 0;
    ptwu->mask = (ptwu->size - 1) << offset;
    ptwu->offset = offset;
    return ptwu;
}

/**
 * @brief Destroy time wheel unit
 * @param pptwu 
 */
void tw_destroy_twu(twu_t **pptwu)
{
    twAssert(pptwu != NULL);
    int i;
    for (i = 0; i < (*pptwu)->size; ++i)
    {
        if((*pptwu)->list[i]->len > 0)
            listDatasFree((*pptwu)->list[i], twFree_);
        listDestroy(&(*pptwu)->list[i]);
    }
    twFree(*pptwu);
    *pptwu = NULL;
}

/**
 * @brief Create a Time Wheel
 * @param levelMax time wheel Max level
 * @param levelx level(1),level(2)...level(Max)
 * @return time wheel base struct handle
 * @note levelx[0] + levelx[1] + ... + levelx[Max] <= 32
 */
twb_t *tw_create_twb(uint32_t levelMax, uint8_t* levelx, uint16_t TickFrequence)
{
    twAssert(levelMax != 0);
    twAssert(levelx != NULL);

    int i;
    twb_t *ptwb = NULL;
    ptwb = twMalloc(sizeof(twb_t) + sizeof(twu_t*) * levelMax);
    if (ptwb == NULL) return NULL;
    ptwb->bitSize = 0;
    for (i = 0; i < levelMax; ++i)
    {
        twAssert(levelx[i] != 0);
        ptwb->twu[i] = tw_create_twu(levelx[i], ptwb->bitSize);
        ptwb->bitSize += levelx[i];
        if(ptwb->twu[i] == NULL)
        {
            ptwb->level = i - 1;
            tw_destroy_twb(&ptwb);
            return NULL;
        }
    }
    twAssert(ptwb->bitSize <= 32);
    ptwb->frequence = TickFrequence;
    ptwb->level = i;
    ptwb->tick = 0;
    ptwb->currentTask = NULL;
    return ptwb;
}

/**
 * @brief Destroy time wheel base
 * @param pptwb 
 */
void tw_destroy_twb(twb_t **pptwb)
{
    twAssert(pptwb != 0);
    int i;
    for (i = 0; i < (*pptwb)->level; ++i)
    {
        tw_destroy_twu(&(*pptwb)->twu[i]);
    }
    twFree(*pptwb);
    *pptwb = NULL;
}

/**
 * @brief Create a timer task
 * @param function timer function
 * @param param function param
 * @param expire expire time (tick)
 * @retval twt_t* || NULL
 */
twt_t *tw_create_twt(uint32_t (*function)(void*), void *param, twTick_t expire, twtID_t ID)
{
    twt_t *ptwt = twMalloc(sizeof(twt_t));
    if(ptwt == NULL) return NULL;
    ptwt->function = function;
    ptwt->param = param;
    ptwt->expire = expire;
    ptwt->id = ID;
    return ptwt;
}

/**
 * @brief Calculate which level ticktime belongs to
 */
twUSE_STATIC_INLINE uint32_t tw_calc_level(twb_t *ptwb, twTick_t ticktime)
{
    uint32_t i = ptwb->level;
    while (i-- > 0)
        if (ptwb->twu[i]->mask & ticktime) break;
    return i;
}

/**
 * @brief Calculate the index of ticktime at level
 */
twUSE_STATIC_INLINE uint32_t tw_calc_index(twu_t *twu ,twTick_t ticktime)
{
    uint32_t expire = ((ticktime & twu->mask) >> twu->offset) + twu->tick;
    return expire >= twu->size ? expire - twu->size : expire;
}

/*-------------------------------------------------------------------
what's ticktime?
    example: twu.size = 256
    means tick = {0 1 2 3 4 5 ... 252 253 254 254 255}
    when tick = 255 and ticktime = 255
                                     ↓(tick)
    0 1 2 3 4 5 ... 251 252 253 254 255
                                 ↑(ticktime)
    Tick needs to be moved 255 times before reaching the ticktime

-------------------------------------------------------------------*/

/**
 * @brief push a time wheel task into the list
 */
twUSE_STATIC_INLINE void tw_push(list_t *root, twt_t *twt)
{
    twAssert(root != NULL);
    listPushh(root, twt);
}

/**
 * @brief pop a time wheel task from the list
 */
twUSE_STATIC_INLINE twt_t *tw_pop(list_t *root)
{
    twAssert(root != NULL);
    return listPoph(root);
}

/**
 * @brief Insert a task into the time wheel
 * @param ptwb 
 * @param ptwt 
 * @retval twTick_t Remaining tick
 */
twTick_t tw_insert_task(twb_t *ptwb, twt_t *ptwt)
{
    twAssert(ptwb != NULL);
    twAssert(ptwt != NULL);

    uint32_t levelIndex = tw_calc_level(ptwb, ptwt->expire);
    uint32_t unitIndex = tw_calc_index(ptwb->twu[levelIndex], ptwt->expire);
    tw_push(ptwb->twu[levelIndex]->list[unitIndex], ptwt);
    return ptwt->expire &= ~ptwb->twu[levelIndex]->mask;
}

/**
 * @brief Create a timer task
 * @param ptwb The time wheel base struct handle
 * @param ticktime Delay time
 * @param function Timer task callback (can't be NULL)
 * @param param Parameters passed to the timer task
 * @param id Task id
 * @retval twStatus 
 */
twStatus tw_task_create(twb_t *ptwb, twTick_t ticktime, uint32_t (*function)(void*), void *param, twtID_t id)
{
    twAssert(function != NULL);
    if(ticktime > (1ULL<<ptwb->bitSize) - 1) return TW_PARAM_ERROR;
    twt_t *ptwt = tw_create_twt(function, param, ticktime, id);
    if (ptwt == NULL) return TW_MALLOC_FAILURE;
    tw_insert_task(ptwb, ptwt);
    return TW_OK;
}

/**
 * @brief Find a timer task using ID 
 * @param ptwb 
 * @param id Task id
 * @retval twt_t* 
 */
twt_t *tw_task_find_id(twb_t* ptwb, twtID_t id)
{
    twAssert(ptwb != NULL);
    uint32_t i, j, k;
    twt_t *ptwt = NULL;
    for (i = ptwb->level - 1; i < ptwb->level; --i)
    {
        for (j = 0; j < ptwb->twu[i]->size; ++j)
        {
            listIteratorh(ptwb->twu[i]->list[j], k)
            {
                ptwt = listRead(ptwb->twu[i]->list[j], k);
                if(ptwt->id == id) return ptwt;
            }
        }
    }
    return NULL;
}


/**
 * @brief Destroy task according to ID
 * @param ptwb 
 * @param id 
 */
void tw_task_destroy_id(twb_t *ptwb, twtID_t id)
{
    twAssert(ptwb != NULL);
    uint32_t i, j, k;
    twt_t *ptwt = NULL;
    for (i = ptwb->level - 1; i < ptwb->level; --i)
    {
        for (j = 0; j < ptwb->twu[i]->size; ++j)
        {
            listIteratorh(ptwb->twu[i]->list[j], k)
            {
                ptwt = listRead(ptwb->twu[i]->list[j], k);
                if(ptwt->id == id)
                {
                    listDeleteNodeAt(ptwb->twu[i]->list[j], k);
                    break;
                }
            }
        }
    }
    if(ptwt == NULL) return;
    twFree(ptwt);
}

/**
 * @brief Destroy task according to function
 * @param ptwb 
 * @param function 
 */
void tw_task_destroy_function(twb_t *ptwb, uint32_t (*function)(void*))
{
    twAssert(ptwb != NULL);
    uint32_t i, j, k;
    twt_t *ptwt = NULL;
    for (i = ptwb->level - 1; i < ptwb->level; --i)
    {
        for (j = 0; j < ptwb->twu[i]->size; ++j)
        {
            listIteratorh(ptwb->twu[i]->list[j], k)
            {
                ptwt = listRead(ptwb->twu[i]->list[j], k);
                if(ptwt->function == function)
                {
                    listDeleteNodeAt(ptwb->twu[i]->list[j], k);
                    if(ptwt == NULL) continue;
                    twFree(ptwt);
                }
            }
        }
    }
}

/**
 * @brief Set param for task
 * @param ptwt 
 * @param param  
 */
twUSE_INLINE void tw_task_set_param(twt_t *ptwt, void *param)
{
    twAssert(ptwt != NULL);
    ptwt->param = param;
}

/**
 * @brief Set param for next timer task
 * @param ptwb 
 * @param param 
 * @note Can only be used in timer tasks
 */
void tw_task_set_param_internal(twb_t *ptwb, void* param)
{
    twAssert(ptwb != NULL);
    twAssert(ptwb->currentTask != NULL);
    ptwb->currentTask->param = param;
}

/**
 * @brief Set param for timer task
 * @param ptwb 
 * @param id
 * @param param 
 */
twStatus tw_task_set_param_id(twb_t *ptwb, twtID_t id, void* param)
{
    twAssert(ptwb != NULL);
    twt_t *ptwt = tw_task_find_id(ptwb, id);
    if (ptwt == NULL) return TW_NOT_FIND;
    ptwt->param = param;
    return TW_OK;
}

/**
 * @brief Get current ticks
 * @param ptwb 
 * @retval uint64_t 
 */
uint64_t tw_get_tick(twb_t *ptwb)
{
    twAssert(ptwb != NULL);
    return ptwb->tick;
}

/**
 * @brief Get current time
 * @param ptwb 
 * @retval uint32_t 
 */
uint64_t tw_get_time(twb_t *ptwb)
{
    twAssert(ptwb != NULL);
    return tw_tick2ms(ptwb, ptwb->tick);
}

/**
 * @brief Check and execute the tasks of the time wheel
 * @param ptwb 
 * @param ptwu 
 */
static void tw_check_wheels(twb_t *ptwb, twu_t *ptwu)
{
    twAssert(ptwb != NULL);
    twAssert(ptwu != NULL);

    int i;
    twt_t* ptwt;
    listIteratort(ptwu->list[ptwu->tick], i)
    {
        //Pop task and check expire from list
        ptwt = tw_pop(ptwu->list[ptwu->tick]);
        twAssert(ptwt != NULL);
        if (ptwt->expire > 0)
        {
            tw_insert_task(ptwb, ptwt);
            continue;
        }
        if (ptwt->function == NULL)
        {
            twFree(ptwt);
            continue;
        }
        ptwb->currentTask = ptwt;
        ptwt->expire = ptwt->function(ptwt->param);
        if (ptwt->expire > 0)
        {
            tw_insert_task(ptwb, ptwt);
        }
        else
        {
            twFree(ptwt);
        }
        ptwb->currentTask = NULL;
        
    }
}

/**
 * @brief Update the tick of the time wheel
 * @param ptwb 
 * @param level 
 */
static void tw_update_time(twb_t *ptwb, uint32_t level)
{
    twAssert(ptwb != NULL);
    twAssert(level < ptwb->level);

    ptwb->twu[level]->tick++;
    if (ptwb->twu[level]->tick >= ptwb->twu[level]->size)
    {
        ptwb->twu[level]->tick = 0;
        tw_check_wheels(ptwb, ptwb->twu[level]);
        if (ptwb->level > level + 1)
        {
            tw_update_time(ptwb, level + 1);
        }
        return;
    }
    tw_check_wheels(ptwb, ptwb->twu[level]);
    
}

/**
 * @brief Run a time wheel
 * @param ptwb 
 * @note Require a periodic call
 */
void tw_run(twb_t* ptwb)
{
    twAssert(ptwb != NULL);
    twAssert(ptwb->level != 0);
    ptwb->tick++;
    //update timer
    tw_update_time(ptwb, 0);
}

